// Created by Roman Novitsky on 27.04.2009


#region Imported Namespaces


using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Xml;

using WtfManager.Framework;
using WtfManager.Utilities;


#endregion


namespace WtfManager.UIManager
{
  public class StateManager: ExtensibleService
  {
    #region Fields


    private static readonly List<DependencyObject> _pendingUIComponents = new List<DependencyObject>();

    public static readonly DependencyProperty DefaultStateFileNameProperty =
      DependencyProperty.RegisterAttached("DefaultStateFileName", typeof(string), typeof(StateManager),
                                          new PropertyMetadata(DefaultStateFileNamePropertyChanged));

    public static readonly DependencyProperty PersistStateProperty = DependencyProperty.RegisterAttached(
      "PersistState", typeof(bool), typeof(StateManager));

    public static readonly RoutedUICommand RestoreStateCommand;
    public static readonly RoutedUICommand SaveStateCommand;

    private readonly TypeKeyDictionary<IStateManagerExtension> _extensions =
      new TypeKeyDictionary<IStateManagerExtension>();


    #endregion


    #region Constructors/Finalizers


    static StateManager()
    {
      SaveStateCommand = new RoutedUICommand("Save State", "SaveStateCommand", typeof(StateManager));
      RestoreStateCommand = new RoutedUICommand("Restore State", "RestoreStateCommand", typeof(StateManager));
    }


    internal StateManager()
    {
    }


    #endregion


    #region Properties/Indexers


    public override string Description
    {
      get
      {
        return "Loads and Saves UI State";
      }
    }


    public override string Name
    {
      get
      {
        return "State Manager";
      }
    }


    #endregion


    #region Methods


    public static void SetPersistState(FrameworkElement frameworkElement, bool value)
    {
      if (frameworkElement == null)
      {
        throw new ArgumentNullException("frameworkElement");
      }

      frameworkElement.SetValue(PersistStateProperty, value);
    }


    public static bool GetPersistState(FrameworkElement frameworkElement)
    {
      if (frameworkElement == null)
      {
        throw new ArgumentNullException("frameworkElement");
      }

      return (bool)frameworkElement.GetValue(PersistStateProperty);
    }


    protected override void OnStateChnaged()
    {
      if (IsInitialized)
      {
        foreach (FrameworkElement frameworkElement in _pendingUIComponents)
        {
          string fileName = GetDefaultStateFileName(frameworkElement);

          LoadDefaultState(fileName, frameworkElement);
        }

        _pendingUIComponents.Clear();
      }
    }


    public static void SetDefaultStateFileName(FrameworkElement frameworkElement, string value)
    {
      if (frameworkElement == null)
      {
        throw new ArgumentNullException("frameworkElement");
      }

      frameworkElement.SetValue(DefaultStateFileNameProperty, value);
    }


    public static string GetDefaultStateFileName(FrameworkElement frameworkElement)
    {
      if (frameworkElement == null)
      {
        throw new ArgumentNullException("frameworkElement");
      }

      return (string)frameworkElement.GetValue(DefaultStateFileNameProperty);
    }


    private static void DefaultStateFileNamePropertyChanged(DependencyObject dependencyObject,
                                                            DependencyPropertyChangedEventArgs e)
    {
      var newValue = e.NewValue as string;

      if (string.IsNullOrEmpty(newValue))
      {
        return;
      }

      if (!File.Exists(newValue))
      {
        return;
      }

      var frameworkElement = dependencyObject as FrameworkElement;

      if (frameworkElement == null)
      {
        return;
      }

      if (!frameworkElement.IsLoaded)
      {
        frameworkElement.Loaded += FrameworkElementLoaded;
      }
      else
      {
        LoadDefaultState(newValue, frameworkElement);
      }
    }


    private static void LoadDefaultState(string defaultStateFileName, FrameworkElement frameworkElement)
    {
      var stateManager = (StateManager)WtfFramework.Instance.Services[typeof(StateManager)];

      if (stateManager.IsInitialized)
      {
        stateManager.RestoreState(defaultStateFileName, frameworkElement);
      }
      else
      {
        _pendingUIComponents.Add(frameworkElement);
      }
    }


    private static void FrameworkElementLoaded(object sender, RoutedEventArgs e)
    {
      var frameworkElement = (FrameworkElement)sender;

      string defaultLayout = GetDefaultStateFileName(frameworkElement);

      if (string.IsNullOrEmpty(defaultLayout))
      {
        return;
      }

      LoadDefaultState(defaultLayout, frameworkElement);
    }


    public void SaveState(string fileName, FrameworkElement frameworkElement)
    {
      Debug.Assert(IsInitialized, "StateManager is not yet initialized.");

      using (var stream = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite))
      {
        var xmlTextWriter = new XmlTextWriter(stream, Encoding.Default);
        xmlTextWriter.Formatting = Formatting.Indented;
        xmlTextWriter.Indentation = 2;

        xmlTextWriter.WriteStartElement("State");
        {
          SaveState(xmlTextWriter, frameworkElement);
        }
        xmlTextWriter.WriteEndElement();

        xmlTextWriter.Close();
        stream.Close();
      }
    }


    private void SaveState(XmlTextWriter writer, DependencyObject dependencyObject)
    {
      var frameworkElement = dependencyObject as FrameworkElement;

      if (frameworkElement != null)
      {
        if (GetPersistState(frameworkElement))
        {
          WriteState(writer, frameworkElement);
        }
      }

      int childrenCount = VisualTreeHelper.GetChildrenCount(dependencyObject);

      for (int i = 0; i < childrenCount; i++)
      {
        DependencyObject child = VisualTreeHelper.GetChild(dependencyObject, i);

        SaveState(writer, child);
      }
    }


    private void WriteState(XmlTextWriter writer, FrameworkElement frameworkElement)
    {
      if (string.IsNullOrEmpty(frameworkElement.Name))
      {
        return;
      }

      IStateManagerExtension extension = GetExtension(frameworkElement);

      if (extension == null)
      {
        return;
      }

      writer.WriteStartElement(frameworkElement.Name);

      extension.SaveState(writer, frameworkElement);

      writer.WriteEndElement();
    }


    private IStateManagerExtension GetExtension(FrameworkElement frameworkElement)
    {
      return _extensions.GetBestValue(frameworkElement.GetType());
    }


    public void RestoreState(string fileName, FrameworkElement frameworkElement)
    {
      Debug.Assert(IsInitialized, "StateManager is not yet initialized.");

      if (!File.Exists(fileName))
      {
        return;
      }

      const string invalidStructureMessage = "State file has an invalid structure!";
      var xmlLayout = new XmlDocument();

      xmlLayout.Load(fileName);

      if (xmlLayout.DocumentElement == null || xmlLayout.DocumentElement.Name != "State")
      {
        Debug.Fail(invalidStructureMessage);
        return;
      }

      RestoreState(xmlLayout.DocumentElement, frameworkElement);
    }

    private void RestoreState(XmlNode stateNode, DependencyObject dependencyObject)
    {
      var frameworkElement = dependencyObject as FrameworkElement;

      if (frameworkElement != null)
      {
        if (GetPersistState(frameworkElement))
        {
          ReadState(stateNode, frameworkElement);
        }
      }

      int childrenCount = VisualTreeHelper.GetChildrenCount(dependencyObject);

      for (int i = 0; i < childrenCount; i++)
      {
        DependencyObject child = VisualTreeHelper.GetChild(dependencyObject, i);

        RestoreState(stateNode, child);
      }
    }

    private void ReadState(XmlNode stateNode, FrameworkElement frameworkElement)
    {
      if(string.IsNullOrEmpty(frameworkElement.Name))
      {
        return;
      }

      XmlElement stateElement = stateNode[frameworkElement.Name];
      IStateManagerExtension extension = GetExtension(frameworkElement);

      if (extension == null || stateElement == null)
      {
        return;
      }

      extension.RestoreState(stateElement, frameworkElement);
    }


    protected override FrameworkComponentState OnLoad()
    {
      return FrameworkComponentState.Loaded;
    }


    protected override void OnUnload()
    {
    }


    protected override void OnExtensionAdded(Service extension)
    {
      var stateManagerExtension = (IStateManagerExtension)extension;

      _extensions.Add(stateManagerExtension.ControlType, stateManagerExtension);
    }


    public override bool IsExtension(Service extension)
    {
      return extension is IStateManagerExtension;
    }


    protected override void OnExtensionRemoved(Service extension)
    {
      var stateManagerExtension = (IStateManagerExtension)extension;

      _extensions.Remove(stateManagerExtension.ControlType);
    }


    protected override FrameworkComponentState OnInitialize()
    {
      return FrameworkComponentState.Initialized;
    }


    #endregion
  }
}